package com.zh.webcommon.common.log;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.zh.webcommon.common.viewdata.CollectionSummarySerializer;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.InputStreamSource;
import org.springframework.http.HttpEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;

import java.io.Closeable;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import static com.zh.webcommon.CommonConst.GSON;

/**
 * 监控访问接口的请求参数、响应数据 及 消耗时间
 *
 * @author ZH
 * @date 16:03 2020/6/22
 */
@Slf4j
@Order(0)
@Aspect
public class AccessMonitorAspect {
    private static final ObjectMapper objectMapper;

    static {
        SimpleModule module = new SimpleModule(); // 创建自定义模块
        // 注册序列化器：
        var summarySerializer = new CollectionSummarySerializer();
        module.addSerializer(Object[].class, summarySerializer);
        module.addSerializer(Collection.class, summarySerializer);

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(module);

        objectMapper = mapper;
    }

    public AccessMonitorAspect() {
        log.info("[AccessMonitorAspect] constructor ...");
    }

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
    public void accessPointcut() {
    }

    /**
     * 环绕通知
     * 记录每个接口的消耗时长
     */
    @Around("accessPointcut()")
    public Object accessApiMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        //请求接口名称
        String accessMethod = joinPoint.getSignature().getName();
        String accessController = joinPoint.getTarget().getClass().getSimpleName();
        String accessMethodFullName = accessController + "." + accessMethod;

        //请求的URI
        String uri = getRequestURI();
        //请求参数
        List<Object> params = getRequestParams(joinPoint);
        //请求头数据
        Map<String, String> headers = getRequestHeaders();

        long startTime = System.currentTimeMillis();
        Object proceed = null;
        try {
            proceed = joinPoint.proceed();
        } finally {
            long endTime = System.currentTimeMillis();

            String info = "=====================> 接口访问信息" + accessMethodFullName + " ↲\n" +
                    String.format("[请求URI] %s%n", uri) +
                    String.format("[请求Headers (x-)]%s%n", printHeaders(headers)) +
                    String.format("[请求参数] %s%n", printRequestData(params)) +
                    String.format("[返回数据] %s%n", printResponseData(proceed)) +
                    String.format("[耗时(ms)] %d ms%n", (endTime - startTime));

            log.info(info);
        }
        return proceed;
    }

    /**
     * 获取请求数据
     */
    private List<Object> getRequestParams(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();

        return Arrays.stream(args).map(e -> {
                    if (e instanceof ServletRequest req) {
                        return req.getParameterMap();
                    }
                    return e;
                }).filter(e -> !notJson(e))
                .toList();
    }

    private String printRequestData(List<Object> params) {
        if (notJson(params)) {
            return params.toString();
        }
        try {
            return GSON.toJson(params);
        } catch (Exception ex) {
            log.warn("格式化Json出错. cause = {}", ex.getMessage());
            return "";
        }
    }

    private String printResponseData(Object proceed) {
        if (proceed == null) return "null";

        if (notJson(proceed)) {
            return proceed.toString();
        }
        try { //此处得用 jackson的，因为要简化List格式化
            return objectMapper.writeValueAsString(proceed);
        } catch (Exception ex) {
            log.warn("格式化Json出错. cause = {}", ex.getMessage());
            return "";
        }
    }

    private String printHeaders(Map<String, String> headers) {
        StringBuilder str = new StringBuilder();
        headers.forEach((k, v) -> {
            str.append(String.format("%n\t%-20s : %s", k, v));
        });
        return str.toString();
    }

    /**
     * 获取请求的URI
     */
    private String getRequestURI() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest().getRequestURI();
    }

    /**
     * 获取请求头headers
     * 注意：排除了前缀‘sec-’
     *
     * @return headers
     */
    private Map<String, String> getRequestHeaders() {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        Map<String, String> headerMap = new TreeMap<>();

        request.getHeaderNames().asIterator()
                .forEachRemaining(e -> {
                    if (e != null && e.toLowerCase().startsWith("x-")) {
                        headerMap.put(e, request.getHeader(e));
                    }
                });

        return headerMap;
    }

    private boolean notJson(Object obj) {
        if (obj == null) return true;

        if (obj.getClass().isArray() || obj instanceof List) {
            List<?> objects = obj.getClass().isArray() ? CollectionUtils.arrayToList(obj) : (List<?>) obj;
            if (objects.isEmpty()) {
                return false;
            } else {
                return notJson(objects.get(0));
            }
        }
        return (obj instanceof ServletRequest || obj instanceof ServletResponse
                || obj instanceof HttpEntity || obj instanceof InputStreamSource || obj instanceof File
                || obj instanceof Closeable || obj instanceof ResponseBodyEmitter);
    }
}